package com.chj.thread.capt02.threadlocal;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * ThreadLocal造成的内存泄漏演示
 * @author Administrator
 *
 */
public class ThreadLocalOOM {

	private static final int TASK_LOOP_SIZE = 500;

	/**
	 * 	总结：JVM利用设置ThreadLocalMap的Key为弱引用，来避免内存泄露。
	 *  JVM利用调用remove、get、set方法的时候，回收弱引用。
	 * 	当ThreadLocal存储很多Key为null的Entry的时候，而不再去调用remove、get、set方法，那么将导致内存泄漏。
	 * 	使用线程池+ ThreadLocal时要小心，因为这种情况下，线程是一直在不断的重复运行的，从而也就造成了value可能造成累积的情况。
	 */
	
	final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,
			new LinkedBlockingQueue<>());

	static class LocalVariable {
		private byte[] a = new byte[1024 * 1024 * 5];/* 5M大小的数组 */
	}

	/**
	 * 	根据我们前面对ThreadLocal的分析，我们可以知道每个Thread 维护一个 ThreadLocalMap，这个映射表的 key 是 ThreadLocal实例本身，
	 *  value 是真正需要存储的 Object，也就是说 ThreadLocal 本身并不存储值，它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。
	 * 	仔细观察ThreadLocalMap，这个map是使用 ThreadLocal 的弱引用作为 Key 的，弱引用的对象在 GC 时会被回收
	 * 
	 * 	当把threadlocal变量置为null以后，没有任何强引用指向threadlocal实例，所以threadlocal将会被gc回收。
	 * 	这样一来，ThreadLocalMap中就会出现key为null的Entry，就没有办法访问这些key为null的Entry的value，
	 *	 如果当前线程再迟迟不结束的话，这些key为null的Entry的value就会一直存在一条强引用链：
	 *  Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value，而这块value永远不会被访问到了，所以存在着内存泄露
	 *  
	 *      只有当前thread结束以后，current thread就不会存在栈中，强引用断开，Current Thread、Map value将全部被GC回收。
	 *     最好的做法是不在需要使用ThreadLocal变量后，都调用它的remove()方法，清除数据
	 *  
	 * 
	 */
	final static ThreadLocal<LocalVariable> localVariable = new ThreadLocal<>();
	
	
	public static void main(String[] args) {
		Object o = new Object();
		/* 5*5=25 */
		for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
			poolExecutor.execute(new Runnable() {
				@Override
				public void run() {
					//当我们启用了ThreadLocal以后,内存占用最高升至150M，一般情况下稳定在90M左右(不使用remove方法，发生了内存泄漏)
					localVariable.set(new LocalVariable());
					new LocalVariable();
	                System.out.println("use local varaible");
	                //使用remove方法后，内存占用也在25M左右，完全和我们不加ThreadLocal表现一样
	              //localVariable.remove();
				}
				
			});
		}
	}

}
